﻿' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic
    Partial Friend Class SymbolDisplayVisitor
        Protected Overrides Function ShouldRestrictMinimallyQualifyLookupToNamespacesAndTypes() As Boolean
            Dim token = semanticModelOpt.SyntaxTree.GetRoot().FindToken(positionOpt)
            Dim startNode = token.Parent

            Return SyntaxFacts.IsInNamespaceOrTypeContext(TryCast(startNode, ExpressionSyntax)) OrElse Me.inNamespaceOrType
        End Function

        Private Sub MinimallyQualify(symbol As INamespaceSymbol, emittedName As String, parentsEmittedName As String)
            Debug.Assert(symbol.ContainingNamespace IsNot Nothing OrElse symbol.IsGlobalNamespace)

            ' NOTE(cyrusn): We only call this once we've already checked if there is an alias that
            ' corresponds to this namespace. 

            If symbol.IsGlobalNamespace Then
                ' nothing to add for global namespace itself
                Return
            End If

            Dim visitedParent = False

            ' Check if the name of this namespace binds to the same namespace symbol.  If so,
            ' then that's all we need to add.  Otherwise, we will add the minimally qualified
            ' version of our parent, and then add ourselves to that.
            Dim symbols = If(
                ShouldRestrictMinimallyQualifyLookupToNamespacesAndTypes(),
                semanticModelOpt.LookupNamespacesAndTypes(positionOpt, name:=symbol.Name),
                semanticModelOpt.LookupSymbols(positionOpt, name:=symbol.Name))
            Dim lookupResult As NamespaceSymbol = Nothing

            If symbols.Length = 1 Then
                lookupResult = TryCast(symbols(0), NamespaceSymbol)
            End If

            ' It is possible to get NamespaceSymbol with compilation extent here.
            ' Let's check this case.
            If lookupResult IsNot Nothing Then
                Debug.Assert(lookupResult.Extent.Kind = NamespaceKind.Module OrElse lookupResult.Extent.Kind = NamespaceKind.Compilation)

                If lookupResult IsNot symbol AndAlso lookupResult.Extent.Kind = NamespaceKind.Compilation Then
                    Dim ns = TryCast(symbol, NamespaceSymbol)

                    If ns IsNot Nothing AndAlso ns.Extent.Kind <> NamespaceKind.Compilation AndAlso
                       lookupResult.Extent.Compilation.GetCompilationNamespace(ns) Is lookupResult Then
                        lookupResult = ns
                    End If
                End If
            End If

            If lookupResult IsNot symbol Then
                ' Just the name alone didn't bind properly.  Add our minimally qualified parent
                ' (if we have one), a dot, and then our name.
                Dim containingNamespace = symbol.ContainingNamespace
                If containingNamespace IsNot Nothing Then
                    If containingNamespace.IsGlobalNamespace Then

                        Debug.Assert(format.GlobalNamespaceStyle = SymbolDisplayGlobalNamespaceStyle.Included OrElse
                                          format.GlobalNamespaceStyle = SymbolDisplayGlobalNamespaceStyle.Omitted OrElse
                                          format.GlobalNamespaceStyle = SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining)

                        If format.GlobalNamespaceStyle = SymbolDisplayGlobalNamespaceStyle.Included Then
                            AddGlobalNamespace(containingNamespace)
                            AddOperator(SyntaxKind.DotToken)

                            visitedParent = True
                        End If
                    Else
                        VisitNamespace(containingNamespace, parentsEmittedName)

                        AddOperator(SyntaxKind.DotToken)
                        visitedParent = True
                    End If
                End If
            End If

            builder.Add(CreatePart(SymbolDisplayPartKind.NamespaceName, symbol, emittedName, visitedParent))
        End Sub

        Private Sub MinimallyQualify(symbol As INamedTypeSymbol)
            ' NOTE(cyrusn): We only call this once we've already checked if there is an alias or
            ' special type that corresponds to this type.
            '
            ' We first start by trying to bind just our name and type arguments.  If they bind to the
            ' symbol that we were constructed from.  Otherwise, we get the minimal name of our
            ' parent, add a dot, and then add ourselves.

            Dim visitedParents As Boolean = False

            If Not (symbol.IsAnonymousType OrElse symbol.IsTupleType) Then
                If Not NameBoundSuccessfullyToSameSymbol(symbol) Then
                    If IncludeNamedType(symbol.ContainingType) Then
                        symbol.ContainingType.Accept(NotFirstVisitor)
                        AddOperator(SyntaxKind.DotToken)
                    ElseIf symbol.ContainingNamespace IsNot Nothing Then
                        If symbol.ContainingNamespace.IsGlobalNamespace Then
                            ' Error symbols are put into the global namespace if the compiler has no
                            ' better guess for it, so we shouldn't go spitting it everywhere.
                            If symbol.TypeKind <> TypeKind.Error Then
                                AddKeyword(SyntaxKind.GlobalKeyword)
                                AddOperator(SyntaxKind.DotToken)
                            End If
                        Else
                            symbol.ContainingNamespace.Accept(NotFirstVisitor)
                            AddOperator(SyntaxKind.DotToken)
                        End If
                    End If

                    visitedParents = True
                End If
            End If

            AddNameAndTypeArgumentsOrParameters(symbol, visitedParents)
        End Sub

        Private Function TryAddAlias(
            symbol As INamespaceOrTypeSymbol,
            builder As ArrayBuilder(Of SymbolDisplayPart)) As Boolean

            Dim [alias] = GetAliasSymbol(symbol)
            If Not [alias] Is Nothing Then
                ' We must verify that the alias actually binds back to the thing it's aliasing. It's
                ' possible there's another symbol with the same name as the alias that binds first

                Dim aliasName = [alias].Name
                Dim boundSymbols = semanticModelOpt.LookupNamespacesAndTypes(positionOpt, name:=aliasName)

                If boundSymbols.Length = 1 Then
                    Dim boundAlias = TryCast(boundSymbols(0), IAliasSymbol)
                    If boundAlias IsNot Nothing AndAlso boundAlias.Target.Equals(symbol) Then
                        builder.Add(CreatePart(SymbolDisplayPartKind.AliasName, [alias], aliasName, False))
                        Return True
                    End If
                End If
            End If

            Return False
        End Function

        Private Function GetAliasSymbol(symbol As INamespaceOrTypeSymbol) As IAliasSymbol
            If Not Me.IsMinimizing Then
                Return Nothing
            End If

            Dim token = semanticModelOpt.SyntaxTree.GetRoot().FindToken(positionOpt)
            Dim startNode = token.Parent

            ' NOTE(cyrusn): If we're in an imports clause, we can't use aliases.
            Dim clause = startNode.AncestorsAndSelf().OfType(Of ImportsClauseSyntax).FirstOrDefault()
            If clause IsNot Nothing Then
                Return Nothing
            End If

            Dim compilation = semanticModelOpt.Compilation

            Dim sourceModule = DirectCast(compilation.SourceModule, SourceModuleSymbol)
            Dim sourceFile = sourceModule.TryGetSourceFile(DirectCast(GetSyntaxTree(DirectCast(semanticModelOpt, SemanticModel)), VisualBasicSyntaxTree))
            Debug.Assert(sourceFile IsNot Nothing)

            If Not sourceFile.AliasImportsOpt Is Nothing Then
                For Each [alias] In sourceFile.AliasImportsOpt.Values
                    If [alias].Alias.Target = DirectCast(symbol, NamespaceOrTypeSymbol) Then
                        Return [alias].Alias
                    End If
                Next
            End If

            Return Nothing
        End Function

        Private Function GetSyntaxTree(semanticModel As SemanticModel) As SyntaxTree
            If Not semanticModel.IsSpeculativeSemanticModel Then
                Return semanticModel.SyntaxTree
            End If

            Debug.Assert(semanticModel.ParentModel IsNot Nothing)
            Debug.Assert(Not semanticModel.ParentModel.IsSpeculativeSemanticModel)
            Return semanticModel.ParentModel.SyntaxTree
        End Function

        Private Function RemoveAttributeSuffixIfNecessary(symbol As INamedTypeSymbol, symbolName As String) As String
            If format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.RemoveAttributeSuffix) AndAlso IsDerivedFromAttributeType(symbol) Then

                Dim nameWithoutAttributeSuffix As String = Nothing
                If symbolName.TryGetWithoutAttributeSuffix(False, nameWithoutAttributeSuffix) Then
                    Dim token = SyntaxFactory.ParseToken(nameWithoutAttributeSuffix)
                    If token.Kind = SyntaxKind.IdentifierToken Then
                        symbolName = nameWithoutAttributeSuffix
                    End If
                End If
            End If

            Return symbolName
        End Function

        Private Function IsDerivedFromAttributeType(ByVal derivedType As INamedTypeSymbol) As Boolean
            Return semanticModelOpt IsNot Nothing AndAlso
                DirectCast(derivedType, NamedTypeSymbol).IsOrDerivedFromWellKnownClass(WellKnownType.System_Attribute,
                                                                                       DirectCast(semanticModelOpt.Compilation, VisualBasicCompilation),
                                                                                       useSiteDiagnostics:=Nothing)
        End Function
    End Class
End Namespace
